package br.mikhas.container.factory;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import br.mikhas.container.config.BeanDefinition;
import br.mikhas.util.Assert;

/**
 * Repository designed to stores resource definition and index it in various
 * ways.
 * 
 * @author Mikhail Domanoski
 * 
 */
class BeanLibrary {

	private static final Logger log = LoggerFactory
			.getLogger(BeanLibrary.class);

	/**
	 * Stores the definitions in a list
	 */
	private List<BeanDefinition> resourceList;

	/**
	 * Stores the definitions and index it by its name
	 */
	private Map<String, BeanDefinition> resourceByName;

	BeanLibrary() {
		this.resourceList = new ArrayList<BeanDefinition>(100);
		this.resourceByName = new HashMap<String, BeanDefinition>(100);
	}

	/**
	 * Adds a single resource into the librarys
	 */
	void addResource(BeanDefinition resource) {
		Assert.notNull(resource);

		BeanDefinition res = this.resourceByName.get(resource.getName());

		if (res != null && !res.equals(resource)) {
			log.warn("Duplicated bean definition name found. " + resource
					+ " overwrites " + res);

			resourceList.remove(res);
		}

		this.resourceList.add(resource);

		this.resourceByName.put(resource.getName(), resource);
	}

	/**
	 * Adds a collection of resources into the library
	 */
	void addResources(Collection<BeanDefinition> resource) {
		for (BeanDefinition res : resource) {
			this.addResource(res);
		}
	}

	/**
	 * Checks if the library contains the named resource
	 */
	boolean containsResource(String name) {
		return this.resourceByName.containsKey(name);
	}

	/**
	 * Gets an array of definitions that matches a passed {@link Matcher}
	 * 
	 * @param matcher
	 *            An Object that checks the acceptance of an
	 *            {@link BeanDefinition}
	 * @return The defnitions that the {@link Matcher} accepts
	 */
	BeanDefinition[] getMatches(Matcher matcher) {
		List<BeanDefinition> defList = new ArrayList<BeanDefinition>();

		for (BeanDefinition res : this.resourceList) {
			if (matcher.matches(res)) {
				defList.add(res);
			}
		}

		BeanDefinition[] definitions = new BeanDefinition[defList.size()];
		return defList.toArray(definitions);
	}

	/**
	 * Returns an array with all resource definitions.
	 */
	BeanDefinition[] getAllResources() {
		BeanDefinition[] defs = new BeanDefinition[this.resourceList.size()];
		return this.resourceList.toArray(defs);
	}

	/**
	 * Gets a named resource definition
	 * 
	 * @param name
	 *            The name of the resource
	 * @return The resource definition
	 */
	BeanDefinition getResource(String name) {
		return this.resourceByName.get(name);
	}

	/**
	 * Removes a resource from the library
	 * 
	 * @param definition
	 *            The resource definition to be removed
	 */
	void removeResource(BeanDefinition definition) {
		this.resourceByName.remove(definition.getName());
		this.resourceList.remove(definition);
	}

	/**
	 * Gets all registered resource of a scope.
	 * 
	 * @param scope
	 *            The scope which the resources belongs to.
	 * @return
	 */
	public BeanDefinition[] getResourcesOfScope(Scope scope) {
		return this.getMatches(new ResourceScopeMatcher(scope.getName()));
	}

	/**
	 * Gets all resources which may be assigned to the required type.
	 * 
	 * @param type
	 *            The type of the resource.
	 * @return
	 */
	public BeanDefinition[] getResourcesOfType(Class<?> type) {
		return this.getMatches(new ResourceTypeMatcher(type));
	}

	/**
	 * Fully cleans the library erasing all resource data from it.
	 */
	public void clean() {
		this.resourceByName.clear();
		this.resourceList.clear();
		this.resourceList = new ArrayList<BeanDefinition>(100);
		this.resourceByName = new HashMap<String, BeanDefinition>(100);
	}

	/**
	 * Used to filter bean definitions
	 * 
	 * @author Mikhail Domanoski
	 * 
	 */
	static interface Matcher {
		/**
		 * Defines the conditions that the definition must match to be accepted
		 */
		boolean matches(BeanDefinition res);
	}

	/**
	 * Filters the resources by it's names
	 * 
	 * @author Mikhail Domanoski
	 * 
	 */
	static class ResourceNameMatcher implements Matcher {

		/**
		 * The name of the required Resource
		 */
		private final String resourceName;

		/**
		 * Instantiate a new matcher and sets the required resource name.
		 * 
		 * @param resourceName
		 */
		public ResourceNameMatcher(String resourceName) {
			Assert.notEmpty(resourceName);
			this.resourceName = resourceName;
		}

		@Override
		public boolean matches(BeanDefinition res) {
			return this.resourceName.equals(res.getName());
		}

	}

	/**
	 * Filters the resources by it's scope
	 * 
	 * @author Mikhail Domanoski
	 * 
	 */
	static class ResourceScopeMatcher implements Matcher {

		/**
		 * The name of the required scope
		 */
		private final String scopeName;

		/**
		 * Instantiate a new matcher and sets the required scope name.
		 * 
		 * @param scopeName
		 */
		public ResourceScopeMatcher(String scopeName) {
			Assert.notEmpty(scopeName);
			this.scopeName = scopeName;
		}

		@Override
		public boolean matches(BeanDefinition res) {
			return this.scopeName.equals(res.getScope());
		}

	}

	/**
	 * Filters the resources by it's type
	 * 
	 * @author Mikhail Domanoski
	 * 
	 */
	static class ResourceTypeMatcher implements Matcher {

		/**
		 * The name of the required scope
		 */
		private final Class<?> type;

		/**
		 * Instantiate a new matcher and sets the required scope name.
		 * 
		 * @param scopeName
		 */
		public ResourceTypeMatcher(Class<?> type) {
			Assert.notNull(type);
			this.type = type;
		}

		@Override
		public boolean matches(BeanDefinition res) {
			return this.type.isAssignableFrom(res.getType());
		}

	}
}
